<!DOCTYPE HTML>
<html>
<head>
  <script type="application/javascript" src="mediaStreamPlayback.js"></script>
</head>
<body>
<pre id="test">
<script type="application/javascript">
  "use strict";

  createHTML({
    title: "MediaStream.clone()",
    bug: "1208371"
  });

  runTest(() => Promise.resolve()
    .then(() => getUserMedia({audio: true, video: true})).then(stream => {
      info("Test clone()ing an audio/video gUM stream");
      var clone = stream.clone();

      checkMediaStreamCloneAgainstOriginal(clone, stream);
      checkMediaStreamTrackCloneAgainstOriginal(clone.getAudioTracks()[0],
                                                stream.getAudioTracks()[0]);
      checkMediaStreamTrackCloneAgainstOriginal(clone.getVideoTracks()[0],
                                                stream.getVideoTracks()[0]);

      isnot(clone.id.length, 0, "Stream clone should have an id string");
      isnot(clone.getAudioTracks()[0].id.length, 0,
            "Audio track clone should have an id string");
      isnot(clone.getVideoTracks()[0].id.length, 0,
            "Audio track clone should have an id string");

      info("Stopping original tracks");
      stream.getTracks().forEach(t => t.stop());

      info("Playing from track clones");
      var test = createMediaElement('video', 'testClonePlayback');
      var playback = new MediaStreamPlayback(test, clone);
      return playback.playMedia(false);
    })
    .then(() => getUserMedia({video: true})).then(stream =>
      getUserMedia({video: true}).then(otherStream => {
        info("Test addTrack()ing a video track to a stream without affecting its clone");
        var track = stream.getTracks()[0];
        var otherTrack = otherStream.getTracks()[0];

        var streamClone = stream.clone();
        var trackClone = streamClone.getTracks()[0];
        checkMediaStreamContains(streamClone, [trackClone], "Initial clone");

        stream.addTrack(otherTrack);
        checkMediaStreamContains(stream, [track, otherTrack],
                                 "Added video to original");
        checkMediaStreamContains(streamClone, [trackClone],
                                 "Clone not affected");

        stream.removeTrack(track);
        streamClone.addTrack(track);
        checkMediaStreamContains(streamClone, [trackClone, track],
                                 "Added video to clone");
        checkMediaStreamContains(stream, [otherTrack],
                                 "Original not affected");

        // Not part of streamClone. Does not get stopped by the playback test.
        otherTrack.stop();
        otherStream.stop();

        var test = createMediaElement('video', 'testClonePlayback');
        var playback = new MediaStreamPlayback(test, streamClone);
        return playback.playMedia(false)
          .then(() => stream.getTracks().forEach(t => t.stop()))
          .then(() => stream.stop());
    }))
    .then(() => getUserMedia({audio: true, video: true})).then(stream => {
      info("Test cloning a stream into inception");
      var clone = stream;
      var clones = Array(10).fill().map(() => clone = clone.clone());
      var inceptionClone = clones.pop();
      checkMediaStreamCloneAgainstOriginal(inceptionClone, stream);
      stream.getTracks().forEach(t => (stream.removeTrack(t),
                                       inceptionClone.addTrack(t)));
      is(inceptionClone.getAudioTracks().length, 2,
         "The inception clone should contain the original audio track and a track clone");
      is(inceptionClone.getVideoTracks().length, 2,
         "The inception clone should contain the original video track and a track clone");

      var test = createMediaElement('video', 'testClonePlayback');
      var playback = new MediaStreamPlayback(test, inceptionClone);
      return playback.playMedia(false)
        .then(() => clones.forEach(c => c.getTracks().forEach(t => t.stop())));
    })
    .then(() => getUserMedia({audio: true, video: true})).then(stream => {
      info("Test adding tracks from many stream clones to the original stream");

      const LOOPS = 3;
      for (var i = 0; i < LOOPS; i++) {
        stream.clone().getTracks().forEach(t => stream.addTrack(t));
      }
      is(stream.getAudioTracks().length, Math.pow(2, LOOPS),
         "The original track should contain the original audio track and all the audio clones");
      is(stream.getVideoTracks().length, Math.pow(2, LOOPS),
         "The original track should contain the original video track and all the video clones");
      stream.getTracks().forEach(t1 => is(stream.getTracks()
                                                .filter(t2 => t1.id == t2.id)
                                                .length,
                                          1, "Each track should be unique"));

      var test = createMediaElement('video', 'testClonePlayback');
      var playback = new MediaStreamPlayback(test, stream);
      return playback.playMedia(false);
    })
    .then(() => {
      info("Testing audio content routing with MediaStream.clone()");
      var ac = new AudioContext();

      var osc1kOriginal = createOscillatorStream(ac, 1000);
      var audioTrack1kOriginal = osc1kOriginal.getTracks()[0];
      var audioTrack1kClone = osc1kOriginal.clone().getTracks()[0];

      var osc5kOriginal = createOscillatorStream(ac, 5000);
      var audioTrack5kOriginal = osc5kOriginal.getTracks()[0];
      var audioTrack5kClone = osc5kOriginal.clone().getTracks()[0];

      return Promise.resolve().then(() => {
        info("Analysing audio output of original stream (1k + 5k)");
        var stream = new MediaStream();
        stream.addTrack(audioTrack1kOriginal);
        stream.addTrack(audioTrack5kOriginal);

        var analyser = new AudioStreamAnalyser(ac, stream);
        return analyser.waitForAnalysisSuccess(array =>
                 array[analyser.binIndexForFrequency(50)] < 50 &&
                 array[analyser.binIndexForFrequency(1000)] > 200 &&
                 array[analyser.binIndexForFrequency(3000)] < 50 &&
                 array[analyser.binIndexForFrequency(5000)] > 200 &&
                 array[analyser.binIndexForFrequency(10000)] < 50)
          .then(() => {
            info("Waiting for original tracks to stop");
            stream.getTracks().forEach(t => t.stop());
            return analyser.waitForAnalysisSuccess(array =>
                     array[analyser.binIndexForFrequency(50)] < 50 &&
                     // WebAudioDestination streams do not handle stop()
                     // XXX Should they? Plan to resolve that in bug 1208384.
                     // array[analyser.binIndexForFrequency(1000)] < 50 &&
                     array[analyser.binIndexForFrequency(3000)] < 50 &&
                     // array[analyser.binIndexForFrequency(5000)] < 50 &&
                     array[analyser.binIndexForFrequency(10000)] < 50);
          })
          .then(() => analyser.disconnect());
      }).then(() => {
        info("Analysing audio output of stream clone (1k + 5k)");
        var stream = new MediaStream();
        stream.addTrack(audioTrack1kClone);
        stream.addTrack(audioTrack5kClone);

        var analyser = new AudioStreamAnalyser(ac, stream);
        return analyser.waitForAnalysisSuccess(array =>
                 array[analyser.binIndexForFrequency(50)] < 50 &&
                 array[analyser.binIndexForFrequency(1000)] > 200 &&
                 array[analyser.binIndexForFrequency(3000)] < 50 &&
                 array[analyser.binIndexForFrequency(5000)] > 200 &&
                 array[analyser.binIndexForFrequency(10000)] < 50)
          .then(() => analyser.disconnect());
      }).then(() => {
        info("Analysing audio output of clone of clone (1k + 5k)");
        var stream = new MediaStream([audioTrack1kClone, audioTrack5kClone]).clone();

        var analyser = new AudioStreamAnalyser(ac, stream);
        return analyser.waitForAnalysisSuccess(array =>
                 array[analyser.binIndexForFrequency(50)] < 50 &&
                 array[analyser.binIndexForFrequency(1000)] > 200 &&
                 array[analyser.binIndexForFrequency(3000)] < 50 &&
                 array[analyser.binIndexForFrequency(5000)] > 200 &&
                 array[analyser.binIndexForFrequency(10000)] < 50)
          .then(() => analyser.disconnect());
      }).then(() => {
        info("Analysing audio output of clone() + addTrack()ed tracks (1k + 5k)");
        var stream =
          new MediaStream(new MediaStream([ audioTrack1kClone
                                          , audioTrack5kClone
                                          ]).clone().getTracks());

        var analyser = new AudioStreamAnalyser(ac, stream);
        return analyser.waitForAnalysisSuccess(array =>
                 array[analyser.binIndexForFrequency(50)] < 50 &&
                 array[analyser.binIndexForFrequency(1000)] > 200 &&
                 array[analyser.binIndexForFrequency(3000)] < 50 &&
                 array[analyser.binIndexForFrequency(5000)] > 200 &&
                 array[analyser.binIndexForFrequency(10000)] < 50)
          .then(() => analyser.disconnect());
      }).then(() => {
        info("Analysing audio output of clone()d tracks in original stream (1k) " +
             "and clone()d tracks in stream clone (5k)");
        var stream = new MediaStream([audioTrack1kClone, audioTrack5kClone]);
        var streamClone = stream.clone();

        stream.getTracks().forEach(t => stream.removeTrack(t));
        stream.addTrack(streamClone.getTracks()[0]);
        streamClone.removeTrack(streamClone.getTracks()[0]);

        var analyser = new AudioStreamAnalyser(ac, stream);
        return analyser.waitForAnalysisSuccess(array =>
                 array[analyser.binIndexForFrequency(50)] < 50 &&
                 array[analyser.binIndexForFrequency(1000)] > 200 &&
                 array[analyser.binIndexForFrequency(3000)] < 50 &&
                 array[analyser.binIndexForFrequency(5000)] < 50)
          .then(() => {
            analyser.disconnect();
            var cloneAnalyser = new AudioStreamAnalyser(ac, streamClone);
            return cloneAnalyser.waitForAnalysisSuccess(array =>
                     array[cloneAnalyser.binIndexForFrequency(1000)] < 50 &&
                     array[cloneAnalyser.binIndexForFrequency(3000)] < 50 &&
                     array[cloneAnalyser.binIndexForFrequency(5000)] > 200 &&
                     array[cloneAnalyser.binIndexForFrequency(10000)] < 50)
              .then(() => cloneAnalyser.disconnect());
          });
      }).then(() => {
        info("Analysing audio output enabled and disabled tracks that don't affect each other");
        var stream = new MediaStream([audioTrack1kClone, audioTrack5kClone]);
        var clone = stream.clone();

        stream.getTracks()[0].enabled = true;
        stream.getTracks()[1].enabled = false;

        clone.getTracks()[0].enabled = false;
        clone.getTracks()[1].enabled = true;

        var analyser = new AudioStreamAnalyser(ac, stream);
        return analyser.waitForAnalysisSuccess(array =>
                 array[analyser.binIndexForFrequency(50)] < 50 &&
                 array[analyser.binIndexForFrequency(1000)] > 200 &&
                 array[analyser.binIndexForFrequency(3000)] < 50 &&
                 array[analyser.binIndexForFrequency(5000)] < 50)
          .then(() => {
            analyser.disconnect();
            var cloneAnalyser = new AudioStreamAnalyser(ac, clone);
            return cloneAnalyser.waitForAnalysisSuccess(array =>
                     array[cloneAnalyser.binIndexForFrequency(1000)] < 50 &&
                     array[cloneAnalyser.binIndexForFrequency(3000)] < 50 &&
                     array[cloneAnalyser.binIndexForFrequency(5000)] > 200 &&
                     array[cloneAnalyser.binIndexForFrequency(10000)] < 50)
            .then(() => cloneAnalyser.disconnect());
          })
          // Restore original tracks
          .then(() => stream.getTracks().forEach(t => t.enabled = true));
      });
    }));
</script>
</pre>
</body>
</html>
